import { getPropertyFromData, setPropertyForData, markNodeData } from './utils';
let nodeId = 0;
/**
 * @export
 * @class Node
 */
export default class Node {
    /**
     *Creates an instance of Node.
     * @param {*} options merge tree opions in node
     * @memberof Node
     */
    constructor(options) {
        this.id = nodeId++;
        this.data = null;
        this.expanded = false;
        this.parent = null;
        this.isEditState = false;
        this.isNew = false;

        Object.keys(options).forEach((name) => {
            this[name] = options[name];
        });

        this._level = 0;
        this._loaded = false;
        this._childNodes = [];
        this._loading = false;
        this.isActived = false;

        if (this.parent) {
            this._level = this.parent._level + 1;
        }
        this.isLeaf = getPropertyFromData(this, 'isLeaf');
        this.visible = true;
        this.store.registerNode(this);

        this.setData(this.data);
    }
    /**
     * set children to nodes
     * @param {*} data
     * @memberof Node
     */
    setData(data) {
        if (!data) return;
        // todo why mounted in data
        if (!Array.isArray(data)) {
            markNodeData(this, data);
        }
        this.data = data;
        this._childNodes = [];

        let children = [];
        if (this._level === 0 && Array.isArray(this.data)) {
            children = this.data;
        } else {
            children = getPropertyFromData(this, 'children') || [];
        }
        children.forEach((child) => {
            this.insertChild({ data: child });
        });
    }
    /**
     * add node as child
     * @param {*} child
     * @param {*} index
     * @return { Node } child
     */
    insertChild(child, index) {
        if (!(child instanceof Node)) {
            Object.assign(child, {
                parent: this,
                store: this.store,
            });
            child = new Node(child);
        }
        child._level = this._level + 1;
        // todo handle name
        if (index === undefined || index < 0) {
            this._childNodes.push(child);
        } else {
            this._childNodes.splice(index, 0, child);
        }
        return child;
    }
    /**
     * node expand and cb
     * @param {*} expandParent
     * @param {*} cb
     * @memberof Node
     */
    expand(expandParent, cb) {
        const done = () => {
            if (expandParent) {
                let parent = this.parent;
                while (parent && parent._level > 0) {
                    parent.expanded = true;
                    parent = parent.parent;
                }
            }
            this.expanded = true;
            cb && cb();
        };
        // todo lazy && load
        done();
    }
    /**
     * insert new Child and delete old child
     * @memberof Node
     */
    updateChildren() {
        const newData = this.getChildren() || [];
        const oldData = this._childNodes.map((node) => node.data);
        const newDataMap = {};
        const newNodes = [];

        newData.forEach((item, index) => {
            if (this.store.nodesMap[item.id]) {
                newDataMap[item.id] = { index, data: item };
            } else {
                newNodes.push({ index, data: item });
            }
        });

        oldData.forEach((item) => {
            if (!newDataMap[item.id]) this.removeChildByData(item);
        });
        newNodes.forEach(({ index, data }) => {
            this.insertChild({ data }, index);
        });
    }
    /**
     * remove child by data
     * @param {*} data
     * @memberof Node
     */
    removeChildByData(data) {
        let targetNode = null;
        this._childNodes.forEach((node) => {
            if (node.data === data) {
                targetNode = node;
            }
        });

        if (targetNode) {
            this.removeChild(targetNode);
        }
    }
    /**
     * remove child
     * @param {*} child
     */
    removeChild(child) {
        const index = this._childNodes.indexOf(child);

        if (index > -1) {
            this.store.deregisterNode(child);
            child.parent = null;
            this._childNodes.splice(index, 1);
        }
    }
    /**
     * remove itSelf
     * @memberof Node
     */
    remove() {
        if (this.store.beforeRemove) {
            this.store.beforeRemove();
        }
        this.parent.removeChild(this);
    }
    /**
     * get data children value
     * @return {*} data[children]
     * @memberof Node
     */
    getChildren() {
        if (!this.data) return null;
        const data = this.data;
        const props = this.store.props;
        const children = props.children || 'children';
        return data[children];
    }
    /**
     * collapse node
     * @memberof Node
     */
    collapse() {
        this.expanded = false;
    }
    /**
     * change edit state
     * @param {*} state
     * @param {*} valid
     * @memberof Node
     */
    changeEditState(state, valid) {
        this.isEditState = state;
    }
    /**
     * handle nodes
     * @readonly
     * @memberof Node
     */
    get computedNodes() {
        let nodes = this._childNodes;
        if (this.store.sortFn) {
            nodes = this.store.sortFn(nodes);
        }
        return nodes;
    }
    /**
     * set the content of node
     * @memberof Node
     * @param {*} value
     */
    set label(value) {
        setPropertyForData(this, 'label', value);
    }
    /**
     * get the content of node
     * @memberof Node
     */
    get label() {
        return getPropertyFromData(this, 'label');
    }
}
